// 一共有5种装饰器可被我们使用：

// 类装饰器
// 属性装饰器
// 方法装饰器
// 访问器装饰器
// 参数装饰器

// 所以应用装饰器其实很像是组合一系列函数，类似于高阶函数和类。 
// 通过装饰器我们可以轻松实现代理模式来使代码更简洁以及实现其它一些更有趣的能力。

// 装饰器的语法十分简单，只需要在想使用的装饰器前加上@符号，装饰器就会被应用到目标上：

// function simpleDecorator(c:myClass) {
//   console.log('---hi I am a decorator---')
// }

// @simpleDecorator
// class myClass {}


// // 类装饰器
// @classDecorator
// class Bird {

//   // 属性装饰器
//   @propertyDecorator
//   name: string;

//   // 方法装饰器
//   @methodDecorator
//   fly(
//     // 参数装饰器
//     @parameterDecorator
//       meters: number
//   ) {}

//   // 访问器装饰器
//   @accessorDecorator
//   get egg() {}
// }

// 执行
// 时机
// 装饰器只在解释执行时应用一次，例如
// function f(C) {
//   console.log('apply decorator');
//   console.log(C);
//   return C
// }

// @f
// class A {}
// // 这里的代码会在终端中打印apply decorator，即便我们其实并没有使用类A。
// // output: apply decorator

// let obj_TS13:A=new A();

// 执行顺序
// 不同类型的装饰器的执行顺序是明确定义的：

// 1.实例成员：参数装饰器 -> 方法 / 访问器 / 属性 装饰器 
// 2. 静态成员:参数装饰器 -> 方法 / 访问器 / 属性 装饰器 
// 3. 构造器: 参数装饰器 
// 4. 类装饰器

// function f(key: string): any {
//   console.log("evaluate: ", key);
//   return function () {
//     console.log("call: ", key);
//   };
// }

// // 3. 构造器: 参数装饰器 4. 类装饰器
// // evaluate:  Class Decorator
// // evaluate:  Constructor Parameter
// // call:  Constructor Parameter
// // call:  Class Decorator

// @f("Class Decorator")
// class C {

//   constructor(@f("Constructor Parameter") foo) { }

//   // 2 静态成员:参数装饰器 -> 方法 / 访问器 / 属性 装饰器 
//   // evaluate:  Static Property
//   // call:  Static Property
//   @f("Static Property")
//   static prop?: number;

//   //  2 静态成员:参数装饰器 -> 方法 / 访问器 / 属性 装饰器 
//   // evaluate:  Static Method
//   // evaluate:  Static Method Parameter
// // call:  Static Method Parameter
// // call:  Static Method
//   @f("Static Method")
//   static method(
//     @f("Static Method Parameter") 
//     foo
//     ){ }

//   //  1.实例成员：参数装饰器 -> 方法 / 访问器 / 属性 装饰器 
//   // evaluate:  Instance Method
//   // evaluate:  Instance Method Parameter
//   // call:  Instance Method Parameter
//   // call:  Instance Method
//   @f("Instance Method")
//   method(

//     @f("Instance Method Parameter")
//     foo) { }
//   //  1.实例成员：参数装饰器 -> 方法 / 访问器 / 属性 装饰器 
//   // evaluate:  Instance Property
//   // call:  Instance Property
//   @f("Instance Property")
//   prop?: number;
// }
// 你也许会注意到执行实例属性prop晚于实例方法method 然而执行静态属性static prop早于静态方法static method。 
// 这是因为对于属性/方法/访问器装饰器而言，执行顺序取决于声明它们的顺序。


// 然而，同一方法中不同参数的装饰器的执行顺序是相反的， 最后一个参数的装饰器会最先被执行：

// function f(key: string): any {
//   console.log("evaluate: ", key);
//   return function () {
//     console.log("call: ", key);
//   };
// }
 
// class C {
//   method(
//     @f("Parameter Foo") foo,
//     @f("Parameter Bar") bar
//   ) {}
// }


// 定义
// 类装饰器
// 类型声明：

// type ClassDecorator0 = <TFunction extends Function>(target: TFunction) => TFunction | void;

// @参数:
// target: 类的构造器。
// @返回:
// 如果类装饰器返回了一个值，她将会被用来代替原有的类构造器的声明。

// 例如我们可以添加一个toString方法给所有的类来覆盖它原有的toString方法。


// type Constructor = { new (...args: any[]): any };
// type Constructor=  typeof C;

// function toString<T extends Constructor >(BaseClass: T) {
//   return class extends BaseClass {
//     toString() {
//       return JSON.stringify(this);
//     }
//   };
// }

// type Constructor = {  new (...args: any[]): any };

// function toString2<T extends Constructor>(BaseClass:T):T { // BaseClass->这里不是实例，是class C
//   console.log(BaseClass);
//   return class extends BaseClass {
//     toString(){
//       return JSON.stringify(this);
//     }
//   } ;
// }

// @toString2
// class C {
//   constructor( ...args:any[]){
//     console.log(args);
//   }
//   foo:string;
//   num:string;
// } 
// type typec=  C

// console.log(new C().toString())
// // -> {"foo":"foo","num":24}


// 遗憾的是装饰器并没有类型保护，这意味着：


// declare function Blah<T>(target: T): T & {foo: number}
 
// @Blah
// class Foo {
//   bar() {
//     return this.foo; // Property 'foo' does not exist on type 'Foo'
//   }
// }
 
// new Foo().foo; // Property 'foo' does not exist on type 'Foo'

// // 这是一个TypeScript的已知的缺陷。 目前我们能做的只有额外提供一个类用于提供类型信息：

// declare function Blah<T>(target: T): T & {foo: number}
 
// class Base {
//   foo: number;
// }
 
// @Blah
// class Foo extends Base {
//   bar() {
//     return this.foo;
//   }
// }
 
// console.log(new Foo().foo);

// 属性装饰器
// 类型声明：

// type PropertyDecorator0 =
//   (target: Object, propertyKey: string | symbol) => void;
// @参数:
// target: 对于静态成员来说是类的构造器，对于实例成员来说是类的原型链。
// propertyKey: 属性的名称。
// @返回:
// 返回的结果将被忽略。

// 除了用于收集信息外，属性装饰器也可以用来给类添加额外的方法和属性。 
// 例如我们可以写一个装饰器来给某些属性添加监听器。

function capitalizeFirstLetter(str: string) {
  return str.charAt(0).toUpperCase() + str.slice(1);
}
 
type listenType=(prev: any, next: any) => void;

// type PropertyDecorator0 =
//   (target: Object, propertyKey: string | symbol) => void;

function observable(target: any, key: string): any {
  // prop -> onPropChange
  const targetKey = "on" + capitalizeFirstLetter(key) + "Change";
//  console.log(target===C.prototype); // 对于实例成员来说是类的原型链

// 这里是定义onFooChange属性的值 ，因为属性的类型是【(fn:listenType) => void;】, 就是方法
  target[targetKey] =
      function (fn: listenType) {
      // console.log(this===c) // this指向实例c
      let prev = this[key];
      Reflect.defineProperty(this, key, {
        set(next) {
          fn(prev, next);
          prev = next;
        }
      })
    };
}
 

class C {
  
  @observable
  foo = -1;
 
  @observable
  bar = "bar";
  
  onFooChange :(fn:listenType) => void ; // 这里是定义onFooChange属性的类型是个方法 所以执行用onFooChange(参数)
  onBarChange :(fn:listenType) => void; // 这里是定义onBarChange属性 
}
 

const c = new C();
 
// // 因为onFooChange属性的类型个方法 所以可以执行
c.onFooChange((prev, next) => {
                    console.log(`prev: ${prev}, next: ${next}`);
                }
                 );
c.onBarChange((prev, next) => {
    console.log(`prev: ${prev}, next: ${next}`);
}
 )
 
c.foo = 100; // -> prev: -1, next: 100
c.foo = -3.14; // -> prev: 100, next: -3.14
c.bar = "baz"; // -> prev: bar, next: baz
c.bar = "sing"; // -> prev: baz, next: sing


// 方法装饰器
// 类型声明：
// type MethodDecorator0 = <T>(
//   target: Object,
//   propertyKey: string | symbol,
//   descriptor: TypedPropertyDescriptor<T>
// ) => TypedPropertyDescriptor<T> | void;

// @参数：
// target: 对于静态成员来说是类的构造器，对于实例成员来说是类的原型链。
// propertyKey: 属性的名称。
// descriptor: 属性的描述器。
// @返回： 如果返回了值，它会被用于替代属性的描述器。

// 方法装饰器不同于属性装饰器的地方在于descriptor参数。 
// 通过这个参数我们可以修改方法原本的实现，添加一些共用逻辑。
// 例如我们可以给一些方法添加打印输入与输出的能力：

// function logger(target: any, 
//    propertyKey: string,
//    descriptor: PropertyDescriptor) {
//     console.log(descriptor);
//   const original = descriptor.value;
//   console.log(original);
//   descriptor.value = function (...args) {
//     console.log('params: ', ...args);
//     const result = original.call(this, ...args);
//     console.log('result: ', result);
//     return result;
//   }
//   console.log(descriptor.value);
// }
 
// class C {
//   @logger
//   add(x: number, y:number ) {
//     return x + y;
//   }
// }
 
// const c = new C();
// c.add(1, 2);
// -> params: 1, 2
// -> result: 3


// 访问器装饰器
// 访问器装饰器总体上讲和方法装饰器很接近，唯一的区别在于描述器中有的key不同：

// 方法装饰器的描述器的key为：
// value
// writable
// enumerable
// configurable

// 访问器装饰器的描述器的key为：
// get
// set
// enumerable
// configurable
// 例如，我们可以将某个属性设为不可变值：

// function immutable(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
//   const original = descriptor.set;
 
//   descriptor.set = function (value: any) {
//     return original.call(this, { ...value })
//   }
// }
 
// class C {
//   private _point = { x: 0, y: 0 }
 
//   @immutable
//   set point(value: { x: number, y: number }) {
//     this._point = value;
//   }
 
//   get point() {
//     return this._point;
//   }
// }
 
// const c = new C();
// const point = { x: 1, y: 1 }
// c.point = point;
 
// console.log(c.point === point)
// // -> false


// 参数装饰器
// 类型声明：

// type ParameterDecorator0 = (
//   target: Object,
//   propertyKey: string | symbol,
//   parameterIndex: number
// ) => void;
// @参数：
// target: 对于静态成员来说是类的构造器，对于实例成员来说是类的原型链。
// propertyKey: 属性的名称(注意是方法的名称，而不是参数的名称)。
// parameterIndex: 参数在方法中所处的位置的下标。
// @返回：
// 返回的值将会被忽略。
// 单独的参数装饰器能做的事情很有限，它一般都被用于记录可被其它装饰器使用的信息。


// 结合
// 对于一些复杂场景， 我们可能需要结合使用不同的装饰器。 
// 例如如果我们不仅想给我们的接口添加静态检查，还想加上运行时检查的能力。

// 我们可以用3个步骤来实现这个功能：

// 标记需要检查的参数 (因为参数装饰器先于方法装饰器执行)。
// 改变方法的descriptor的value的值，先运行参数检查器，如果失败就抛出异常。
// 运行原有的接口实现。

// type Validator = (x: any) => boolean;
 
// // save the marks
// // https://zhuanlan.zhihu.com/p/356662885 Record的说明
// const validateMap: Record<string, Validator[]> = {};
// //  console.log(validateMap);
// // 1. 标记需要检查的参数 保存到 validateMap

// // { sayRepeat: [ 0：Function (anonymous),1：Function (anonymous) ] }
// // 1：Function (anonymous) ---》》(x) => Number.isInteger(x)
// // 0：Function (anonymous) ---》》(x) => typeof x === 'string'

// // type ParameterDecorator0 = (
// //   target: Object,
// //   propertyKey: string | symbol,
// //   parameterIndex: number
// // ) => void;
// function typedDecoratorFactory(validator: Validator): ParameterDecorator {
//   // console.log(validator);// (x) => Number.isInteger(x)
//   return (_, key, index) => {
//     // console.log(_); // c
//     // console.log(key); // sayRepeat
//     // console.log(index); //index 0||1
//     const target = validateMap[key as string] ?? [];
//     // console.log(target);
//     // for (key in validateMap){
//     //   console.log(key);
//     // }

//     target[index] = validator;
//     // console.log(target);

//     validateMap[key as string] = target;
//       console.log(validateMap);
//   }
// }

// // declare type MethodDecorator2 = <T>(target:Object, propertyKey: string | symbol,         
// // descriptor: TypedPropertyDescriptor<T>) => TypedPropertyDescriptor<T> | void;
 
// function validate(target: Object, propertyKey: string, descriptor: PropertyDescriptor) {

//   const originalFn = descriptor.value;
//   console.log(originalFn); // [Function: sayRepeat]

//   descriptor.value = function(...args: any[])
//    {
//     console.log(args); // [ 'hello', 2 ]
 
//     // 2. 运行检查器
//     const validatorList = validateMap[propertyKey]; 
//     console.log(validatorList); //[ [Function (anonymous)], [Function (anonymous)] ]
//     // { sayRepeat: [ [Function (anonymous)], [Function (anonymous)] ] }

//     if (validatorList) {
//       args.forEach((arg, index) => {
//         const validator = validatorList[index];
 
//         if (!validator) return;
        
//         console.log(arg); // 遍历 hello，2
//         console.log(validator);
//         const result = validator(arg); // (x) => typeof x === 'string'  (x) => Number.isInteger(x)
 
//         if (!result) {
//           throw new Error(
//             `Failed for parameter: ${arg} of the index: ${index}`
//           );
//         }
//       });
//     }
 
//     // 3. 运行原有的方法
//     return originalFn.call(this, ...args);
//   }
// }
 
// const isInt = typedDecoratorFactory((x) => Number.isInteger(x));
// const isString2 = typedDecoratorFactory((x) => typeof x === 'string');
 
// class C {
//   @validate
//   sayRepeat(@isString2 word: string, @isInt x: number) {
//     console.log(Array(x).fill(word).join(','));
//     return Array(x).fill(word).join('');
//   }
  
//   // @validate
//   // sayRepeat2(@isString2 word: string, @isInt x: number) {
//   //   return Array(x).fill(word).join('');
//   // }
//   // @validate
//   // sayRepeat( @isInt x: number) {
//   //   return Array(x).fill("word").join(',');
//   // }
// }
 
// const c = new C();
// // c.sayRepeat(2); // pass
// c.sayRepeat('hello', 2); // pass
// // c.sayRepeat('', 'lol' as any); // throw an error

// debugger;